package com.xsq.netty;

import android.content.Context;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;

import com.blankj.utilcode.util.GsonUtils;
import com.google.gson.JsonObject;
import com.xsq.netty.bean.MessageBean;
import com.xsq.netty.channel.INettyChannelListener;
import com.xsq.netty.channel.NettyChannelInitializer;
import com.xsq.netty.utils.NettyMsgUtil;
import com.xsq.netty.utils.LogUtils;

import java.io.File;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * 实现Netty功能类 目前只支持单挑tcp连接，后续需要多条连接可扩展
 * Created by Shiquan.Xiao on 2023/2/23.
 */
class NettyClient implements INettyClient{
    private volatile static INettyClient instance;
    private NettyConnection connection;
    private Bootstrap bootstrap;
    private INettyChannelListener listener;
    private final Map<IBinder, INettyListener> listenerMap = new HashMap<>();
    //负责重连线程

    private ExecutorService connectPool;
    //是否释放资源
    private boolean isRelease;
    private boolean isConnectServer;
    private Context mContext;
    private NettyConfig config;

    private NettyClient(){
    }

    @Override
    public synchronized boolean init(Context context, NettyConfig config) {
        mContext = context;
        this.config = config;
        if (config.isOpenLog) {
            /* ------ 日志初始化 ------ */
            String logDirName = "xlog";
            File sdcardLog = new File("/sdcard/rishunVoiceLog", mContext.getPackageName());
            if (!sdcardLog.exists()) {
                sdcardLog.mkdirs();
            }
            File sdcardLogCache = new File("/sdcard/rishunVoiceLog", mContext.getPackageName() + "/" + logDirName);
            if (!sdcardLogCache.exists()) {
                sdcardLogCache.mkdirs();
            }
            LogUtils.init("tcp", sdcardLog.getPath(), sdcardLogCache.getPath(), true);
        }

        initBootstrap();
        createConnectPool();
        return true;
    }

    protected static INettyClient getInstance() {
        if (null == instance) {
            synchronized (NettyHelper.class) {
                if (null == instance) {
                    instance = new NettyClient();
                }
            }
        }
        return instance;
    }

    /**1
     * 初始化bootstrap
     */
    @Override
    public void initBootstrap() {
        EventLoopGroup group = new NioEventLoopGroup(4);
        bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class);
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
        bootstrap.option(ChannelOption.TCP_NODELAY, true);
        // 连接超时时长
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.connectTimeout);
        bootstrap.handler(new NettyChannelInitializer());
        isRelease = false;
    }

    @Override
    public void createConnectPool() {
        if (connectPool == null) {
            connectPool = Executors.newSingleThreadExecutor();
        }
    }

    @Override
    public void connect() {
        if (config == null || !config.isService || config.host == null || config.host.isEmpty()) {
            LogUtils.e(NettyHelper.TAG, "条件不满足，无法连接！");
            return;
        }
        synchronized (this) {
            if (bootstrap == null) {
                initBootstrap();
            }
            //先关闭当前连接，只保留一个连接
            closeConnection();
            createConnectPool();
            connection = new NettyConnection.Builder()
                    .connectPool(connectPool)
                    .bootstrap(bootstrap)
                    .listener(new INettyChannelListener() {
                        @Override
                        public void onConnect() {
                            notifyRemoteListener(0, "");
                        }

                        @Override
                        public void onMessage(String msg) {
                            notifyRemoteListener(1, msg);
                        }

                        @Override
                        public void onFailed() {
                            isConnectServer = false;
                            notifyRemoteListener(2, "");
                        }
                    })
                    .nettyConfig(config)
                    .build();
            connection.connect();
        }
    }

    /**
     * 连接成功
     */
    private void connected() {
        if (config.isEstablishConnectionSend) {
            if (config.connectedMsg != null) {
                for (String str : config.connectedMsg) {
                    try {
                        Thread.sleep(config.connectedSendDelayed);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    sendMessage(str);
                }
            }
            if (config.isConnectedSendIp) {
                try {
                    Thread.sleep(config.connectedSendDelayed);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //本地ip
                SocketAddress socketAddress = connection.getChannel().localAddress();
                String hostAddress = ((InetSocketAddress) socketAddress).getAddress().getHostAddress();
                //发送本地网络ip地址
                sendMessage(NettyMsgUtil.getMsg(config.deviceAddress, NettyHelper.LOCAL_IP_ADDRESS, hostAddress));
            }
        }
    }

    /**
     * tcp消息通知
     */
    private void notifyRemoteListener(int method, String msg) {
        //其他应用监听
        Set<IBinder> iBinderSet = listenerMap.keySet();
        Iterator<IBinder> iterator = iBinderSet.iterator();
        //是否校验与服务连接成功
        if (method == 0) {
            if (listener != null) {
                listener.onConnect();
            }
            while (iterator.hasNext()) {
                IBinder next = iterator.next();
                INettyListener iNettyListener = listenerMap.get(next);
                if (iNettyListener == null) continue;
                try {
                    iNettyListener.onConnect();
                } catch (DeadObjectException DeadOE) {
                    iterator.remove();
                } catch (RemoteException e) {
                    throw new RuntimeException(e);
                }
            }

            if (config.isNeedInitConnect) {
                try {
                    Thread.sleep(config.connectedSendDelayed);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                sendMessage(NettyMsgUtil.getMsg(config.deviceAddress, NettyHelper.INITIAL_CONNECTION, ""));
            } else {
                isConnectServer = true;
                connected();
            }
        } else if (method == 1) {
            if (listener != null) {
                listener.onMessage(msg);
            }
            while (iterator.hasNext()) {
                IBinder next = iterator.next();
                INettyListener iNettyListener = listenerMap.get(next);
                if (iNettyListener == null) continue;
                try {
                    iNettyListener.onMessage(msg);
                } catch (DeadObjectException DeadOE) {
                    iterator.remove();
                } catch (RemoteException e) {
                    throw new RuntimeException(e);
                }
            }
            if (config.isNeedInitConnect) {
                if (!isConnectServer) {
                    MessageBean msgBean = null;
                    try {
                        msgBean = GsonUtils.fromJson(msg, MessageBean.class);
                    } catch (Exception e) {
                        LogUtils.e(NettyHelper.TAG, "接收json数据，解析异常，异常消息为："+msg);
                    }
                    if (msgBean != null && msgBean.type == NettyHelper.DEVICE_ON) {
                        isConnectServer = true;
                        connected();
                    }
                }
            }

        } else {
            if (listener != null) {
                listener.onFailed();
            }
            while (iterator.hasNext()) {
                IBinder next = iterator.next();
                INettyListener iNettyListener = listenerMap.get(next);
                if (iNettyListener == null) continue;
                try {
                    iNettyListener.onFail();
                } catch (DeadObjectException DeadOE) {
                    iterator.remove();
                } catch (RemoteException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    @Override
    public void sendMessage(JsonObject msg) {
        connection.sendMessage(msg);
    }

    @Override
    public void sendMessage(String msg) {
        connection.sendMessage(msg);
    }

    @Override
    public void setHeartbeatMsg(String msg) {
        config.heartbeatMsg = msg;
    }

    @Override
    public NettyConfig nettyConfig() {
        return config;
    }

    @Override
    public Channel getCurrentChannel() {
        return connection.getChannel();
    }

    @Override
    public void setListener(INettyChannelListener listener) {
        this.listener = listener;
    }

    @Override
    public void addNettyListener(IBinder listener) {
        if (listenerMap.containsKey(listener)) return;
        listenerMap.put(listener, INettyListener.Stub.asInterface(listener));
    }

    @Override
    public void removeNettyListener(IBinder listener) {
        listenerMap.remove(listener);
    }

    public Context getContext() {
        return mContext;
    }

    /**
     * 关闭当前连接
     */
    @Override
    public void closeConnection() {
        if (connection != null) {
            connection.closeConnect();
            connection = null;
        }
    }

    @Override
    public boolean isConnectServer() {
        return isConnectServer;
    }

    @Override
    public void setConnectServer(boolean connectServer) {
        isConnectServer = connectServer;
    }

    /**
     * 释放资源
     */
    @Override
    public void release() {
        isRelease = true;
        closeConnection();
        shutdownConnect();
        shutdownLoopGroup();
    }

    @Override
    public void closeConnect() {
        release();
        if (listener != null) {
            listener = null;
        }
        listenerMap.clear();
    }

    @Override
    public boolean isConnected() {
        return connection != null && connection.getStatus() == NettyConnection.ConnectStatus.SUCCESSFUL;
    }

    @Override
    public boolean haveConnection() {
        return connection != null && connection.haveConnection();
    }

    @Override
    public boolean isRelease() {
        return isRelease;
    }

    @Override
    public void shutdownLoopGroup() {
        if (bootstrap != null) {
            try {
                bootstrap.group().shutdownGracefully();
            } catch (Exception ex) {
                LogUtils.e(NettyHelper.TAG, "关闭eventLoop出错，exception：" + ex.getMessage());
            } finally {
                bootstrap = null;
            }
        }
    }

    @Override
    public void shutdownConnect() {
        if (connectPool != null) {
            try {
                connectPool.shutdownNow();
            } catch (Throwable throwable) {
                LogUtils.e(NettyHelper.TAG, "关闭连接线程池出错，exception：" + throwable.getMessage());
            } finally {
                connectPool = null;
            }
        }
    }

}
